import * as ws from "ws";
import { Queue } from "../../../Support/Share/Lib/Collections/Queue";
import { Log } from "../Log/Log";

import { AChannel, ChannelType } from "./AChannel";
import { ServiceType } from "./AService";
import { ErrorCore } from "./ErrorCore";
import { NetServices } from "./NetServices";
import { WService } from "./WService";
import { ETTask } from "../../../Support/Share/Lib/Async/ETTask";
import { MacroDefine } from "../../../Support/Share/Global/MacroDefine";
import { SerializeHelper } from "../../Serialize/SerializeHelper";
import { GetTypeOf } from "../../../Decorator/Decorator";

export class WChannel extends AChannel {

    private readonly Service: WService;

    private readonly webSocket: ws.WebSocket | WebSocket;

    private readonly queue = new Queue<Buffer>();

    private isSending: boolean;

    private isConnected: boolean;

    constructor(id: number, webSocket: ws.WebSocket | WebSocket, service: WService) {
        super();
        this.Id = id;
        this.webSocket = webSocket;
        this.Service = service;
        this.ChannelType = ChannelType.Accept;
    }

    public async initWebSocket() {
        if (this.webSocket instanceof ws.WebSocket) {
            this.webSocket.on("message", (data: ws.RawData, isBinary: boolean) => {
                // console.log(`websocket message : `, data.toString());
                // console.log(data, isBinary);

                this.OnRead(data as Buffer);
            });
            this.webSocket.on("error", (err: Error) => {
                Log.Error(`websocket error : `, err);

                this.OnError(ErrorCore.ERR_WebsocketError);
            });
            this.webSocket.on("close", (code: number, reason: Buffer) => {
                // console.log(`websocket close : `, code, reason);

                this.OnError(ErrorCore.ERR_WebsocketError);
            });
            this.isConnected = true;
        }
        return ETTask.CompletedTask;
    }

    public async initServerWebSocket() {
        let tcs = ETTask.Create<void>();
        if (this.webSocket instanceof ws.WebSocket) {
            this.webSocket.on("open", () => {
                // console.log(`websocket open : `);

                this.isConnected = true;
                tcs.SetResult();
            });
            this.webSocket.on("message", (data: ws.RawData, isBinary: boolean) => {
                // console.log(`websocket message : `, data.toString());
                // console.log(data, isBinary);

                this.OnRead(data as Buffer);
            });
            this.webSocket.on("error", (err: Error) => {
                Log.Error(`websocket error : `, err);

                this.OnError(ErrorCore.ERR_WebsocketError);
            });
            this.webSocket.on("close", (code: number, reason: Buffer) => {
                // console.log(`websocket close : `, code, reason);

                this.OnError(ErrorCore.ERR_WebsocketError);
            });
        }
        return tcs;
    }

    public async initClientWebSocket() {
        let tcs = ETTask.Create<void>();
        if (this.webSocket instanceof WebSocket) {
            this.webSocket.onopen = (evt: Event) => {
                // console.log(`websocket open : `);
                // console.log(evt);

                this.isConnected = true;
                tcs.SetResult();
            };
            this.webSocket.onmessage = (evt: MessageEvent) => {
                let data = evt.data;

                // console.log(`websocket message : `, data.toString());
                // console.log(data);

                this.OnServerMessageRead(data);
            };
            this.webSocket.onerror = (evt: Event) => {
                let err = evt;

                Log.Error(`websocket error : `, err);

                this.OnError(ErrorCore.ERR_WebsocketError);
            };
            this.webSocket.onclose = (evt: CloseEvent) => {
                let code = evt.code;
                let reason = evt.reason;

                // console.log(`websocket close : `, code, reason);

                this.OnError(ErrorCore.ERR_WebsocketError);
            };
        }
        return tcs;
    }

    private async OnServerMessageRead(data) {
        let tsc = ETTask.Create<Uint8Array>();
        let reader = new FileReader();
        reader.onload = (evt) => {
            try {
                let resultBuf = reader.result as ArrayBuffer;
                let buffer = new Uint8Array(resultBuf);
                tsc.SetResult(buffer);
            } catch (error) {
                tsc.SetException(error);
            }
        }
        reader.readAsArrayBuffer(data);
        let buffer = await tsc;
        this.OnRead(buffer as Buffer);
    }

    public Send(actorId: number, stream: Buffer) {
        switch (this.Service.ServiceType) {
            case ServiceType.Inner:
                // console.log(`WChannel Send actorId = ${actorId}`);
                // console.log(stream);
                if (stream instanceof ArrayBuffer) {
                    const uint8Array = new Uint8Array(stream);
                    const buffer = Buffer.alloc(stream.byteLength + 4);
                    buffer.writeInt32BE(actorId);
                    buffer.set(uint8Array, 4);
                    stream = Buffer.from(buffer);
                }
                // console.log(stream);
                break;
            case ServiceType.Outer:
                break;
        }

        if (MacroDefine.ET_SERVER) {
            // console.log(`WChannel Send On Server : `);
            // console.log(typeof stream);
            // console.log(stream);

            this.queue.Enqueue(stream);
        } else {
            this.queue.Enqueue(Buffer.from(stream));
        }

        if (this.isConnected) {
            this.StartSend();
        }
    }

    public async StartSend() {
        if (this.IsDisposed) {
            return;
        }
        try {
            if (this.isSending) {
                return;
            }
            this.isSending = true;
            while (true) {
                if (this.queue.Count == 0) {
                    this.isSending = false;
                    return;
                }
                let data = this.queue.Dequeue();
                try {
                    this.webSocket.send(data);
                    if (this.IsDisposed) {
                        return;
                    }
                } catch (e) {
                    Log.Error(e);
                    this.OnError(ErrorCore.ERR_WebsocketSendError);
                    return;
                }
            }
        }
        catch (e) {
            Log.Error(e);
        }
    }

    public async StartRecv() {
        if (this.IsDisposed) {
            return;
        }
        try {

        } catch (e) {
            Log.Error(e);
            this.OnError(ErrorCore.ERR_WebsocketRecvError);
        }
    }

    private OnRead(data: Buffer) {
        try {
            let channelId = this.Id;
            let message = null;
            let actorId = 0;
            // console.log(`WChannel OnRead`);
            switch (this.Service.ServiceType) {
                case ServiceType.Outer:
                    {
                        if (MacroDefine.ET_SERVER) {
                            let opcode = data.readInt32BE(0);
                            let tsbuffer = data.subarray(4);

                            let type = NetServices.Instance.GetType(opcode);
                            message = SerializeHelper.Deserialize(type, tsbuffer);

                            console.log(`opcode = ${opcode}`);
                            console.log(`message = ${GetTypeOf(message)}`);
                            console.log(message);
                            console.log(typeof message);
                        } else {
                            let view = new DataView(data.buffer);
                            let opcode = view.getUint32(0);
                            let tsbuffer = data.subarray(4);

                            let type = NetServices.Instance.GetType(opcode);
                            message = SerializeHelper.Deserialize(type, tsbuffer);

                            console.log(`opcode = ${opcode}`);
                            console.log(`message = ${GetTypeOf(message)}`);
                            console.log(message);
                            console.log(typeof message);
                        }
                        break;
                    }
                case ServiceType.Inner:
                    {
                        actorId = data.readInt32BE(0);
                        let opcode = data.readInt32BE(4);
                        let tsbuffer = data.subarray(8);

                        let type = NetServices.Instance.GetType(opcode);
                        message = SerializeHelper.Deserialize(type, tsbuffer);

                        console.log(`actorId = ${actorId}`);
                        console.log(`opcode = ${opcode}`);
                        console.log(`message = ${GetTypeOf(message)}`);
                        console.log(message);
                        console.log(typeof message);
                        break;
                    }
            }

            NetServices.Instance.OnRead(this.Service.Id, channelId, actorId, message);
        } catch (e) {
            Log.Error(`${this.RemoteAddress} {memoryStream.Length} ${e}`);
            // 出现任何消息解析异常都要断开Session，防止客户端伪造消息
            this.OnError(ErrorCore.ERR_PacketParserError);
        }
    }

    private OnError(error: number) {
        Log.Info(`WChannel error: ${error} ${this.RemoteAddress}`);
        let channelId = this.Id;
        this.Service.Remove(channelId);
        NetServices.Instance.OnError(this.Service.Id, channelId, error);
    }

    public Dispose(): void {
    }

}